#ifndef CDB_DEBUGGER_COMMANDS_H
#define CDB_DEBUGGER_COMMANDS_H

#include <wx/string.h>
#include <wx/regex.h>
#include <wx/tipwin.h>
#include <globals.h>
#include <manager.h>
#include <M_Code_debugger_interfaces.h>
#include "debugger_defs.h"
#include "Debugger.h"
#include "debuggermanager.h"
#include "parsewatchvalue.h"

static wxRegEx reProcessInf( _T( "id:[ \t]+([A-Fa-f0-9]+)[ \t]+create" ) );
static wxRegEx reWatch( _T( "(\\+0x[A-Fa-f0-9]+ )" ) );
static wxRegEx reBT1( _T( "([0-9]+) ([A-Fa-f0-9]+) ([A-Fa-f0-9]+) ([^[]*)" ) );
static wxRegEx reBT2( _T( "\\[(.+)[ \\t]@[ \\t]([0-9]+)\\][ \\t]*" ) );
static wxRegEx reDisassembly( _T( "^[0-9]+[ \t]+([A-Fa-f0-9]+)[ \t]+[A-Fa-f0-9]+[ \t]+(.*)$" ) );
static wxRegEx reDisassemblyFile( _T( "[0-9]+[ \t]+([A-Fa-f0-9]+)[ \t]+[A-Fa-f0-9]+[ \t]+(.*)\\[([A-z]:)(.*) @ ([0-9]+)\\]" ) );
static wxRegEx reDisassemblyFunc( _T( "^\\(([A-Fa-f0-9]+)\\)[ \t]+" ) );
static wxRegEx reSwitchFrame( _T( "[ \\t]*([0-9]+)[ \\t]([0-9a-z]+)[ \\t](.+)[ \\t]\\[(.+)[ \\t]@[ \\t]([0-9]+)\\][ \\t]*" ) );


class CdbCmd_AddSourceDir : public DebuggerCmd {
  public:
    CdbCmd_AddSourceDir( DebuggerDriver* driver, const wxString& dir )
      : DebuggerCmd( driver ) {
      m_Cmd << _T( "directory " ) << dir;
    }
    void ParseOutput( const wxString& output ) {
      if( output.StartsWith( _T( "Warning: " ) ) ) {
        m_pDriver->Log( output.BeforeFirst( _T( '\n' ) ) );
      }
    }
};


class CdbCmd_SetDebuggee : public DebuggerCmd {
  public:
    CdbCmd_SetDebuggee( DebuggerDriver* driver, const wxString& file )
      : DebuggerCmd( driver ) {
      m_Cmd << _T( "file " ) << file;
    }
    void ParseOutput( const wxString& output ) {
      m_pDriver->Log( output.BeforeFirst( _T( '\n' ) ) );
    }
};


class CdbCmd_AddSymbolFile : public DebuggerCmd {
  public:
    CdbCmd_AddSymbolFile( DebuggerDriver* driver, const wxString& file )
      : DebuggerCmd( driver ) {
      m_Cmd << _T( "add-symbol-file " ) << file;
    }
    void ParseOutput( const wxString& output ) {
      m_pDriver->Log( output.AfterFirst( _T( '\n' ) ).BeforeLast( _T( '\n' ) ) );
    }
};


class CdbCmd_SetArguments : public DebuggerCmd {
  public:
    CdbCmd_SetArguments( DebuggerDriver* driver, const wxString& args )
      : DebuggerCmd( driver ) {
      m_Cmd << _T( "set args " ) << args;
    }
    void ParseOutput( M_Code_unused const wxString& output ) {
    }
};


class CdbCmd_GetPID : public DebuggerCmd {
  public:
    CdbCmd_GetPID( DebuggerDriver* driver )
      : DebuggerCmd( driver ) {
      m_Cmd << _T( "|." );
    }
    void ParseOutput( const wxString& output ) {
      wxArrayString lines = GetArrayFromString( output, _T( '\n' ) );
      for( unsigned int i = 0; i < lines.GetCount(); ++i ) {
        if( reProcessInf.Matches( lines[i] ) ) {
          wxString hexID = reProcessInf.GetMatch( lines[i], 1 );
          long pid;
          if( hexID.ToLong( &pid, 16 ) ) {
            m_pDriver->SetChildPID( pid );
          }
        }
      }
    }
};


class CdbCmd_AttachToProcess : public DebuggerCmd {
  private:
    int m_pid;
  public:
    CdbCmd_AttachToProcess( DebuggerDriver* driver, int pid )
      : DebuggerCmd( driver ),
        m_pid( pid ) {
      m_Cmd << _T( "attach " ) << F( _T( "%d" ), pid );
    }
    void ParseOutput( const wxString& output ) {
      wxArrayString lines = GetArrayFromString( output, _T( '\n' ) );
      for( unsigned int i = 0; i < lines.GetCount(); ++i ) {
        if( lines[i].StartsWith( _T( "Attaching" ) ) ) {
          m_pDriver->Log( lines[i] );
          m_pDriver->SetChildPID( m_pid );
        } else if( lines[i].StartsWith( _T( "Can't " ) ) ) {
          m_pDriver->Log( lines[i] );
          m_pDriver->QueueCommand( new DebuggerCmd( m_pDriver, _T( "quit" ) ) );
        }
      }
    }
};


class CdbCmd_Detach : public DebuggerCmd {
  public:
    CdbCmd_Detach( DebuggerDriver* driver )
      : DebuggerCmd( driver ) {
      m_Cmd << _T( ".detach" );
    }
    void ParseOutput( const wxString& output ) {
      m_pDriver->Log( output );
    }
};


class CdbCmd_Continue : public DebuggerContinueBaseCmd {
  public:
    CdbCmd_Continue( DebuggerDriver* driver )
      : DebuggerContinueBaseCmd( driver, _T( "g" ) ) {
    }
    virtual void Action() {
      m_pDriver->NotifyDebuggeeContinued();
    }
};


class CdbCmd_AddBreakpoint : public DebuggerCmd {
    static int m_lastIndex;
  public:
    CdbCmd_AddBreakpoint( DebuggerDriver* driver, cb::shared_ptr<DebuggerBreakpoint> bp )
      : DebuggerCmd( driver ),
        m_BP( bp ) {
      if( bp->enabled ) {
        if( bp->index == -1 ) {
          bp->index = m_lastIndex++;
        }
        wxString out = m_BP->filename;
        QuoteStringIfNeeded( out );
        m_Cmd << _T( "bu" ) << F( _T( "%ld" ), ( int ) bp->index ) << _T( ' ' );
        if( m_BP->temporary ) {
          m_Cmd << _T( "/1 " );
        }
        if( bp->func.IsEmpty() ) {
          m_Cmd << _T( '`' ) << out << _T( ":" ) << F( _T( "%d" ), bp->line ) << _T( '`' );
        } else
        { m_Cmd << bp->func; }
        bp->alreadySet = true;
      }
    }
    void ParseOutput( const wxString& output ) {
      wxArrayString lines = GetArrayFromString( output, _T( '\n' ) );
      for( unsigned int i = 0; i < lines.GetCount(); ++i ) {
        if( lines[i].StartsWith( _T( "*** " ) ) ) {
          m_pDriver->Log( lines[i] );
        }
      }
    }
    cb::shared_ptr<DebuggerBreakpoint> m_BP;
};

int CdbCmd_AddBreakpoint::m_lastIndex = 1;

class CdbCmd_RemoveBreakpoint : public DebuggerCmd {
  public:
    CdbCmd_RemoveBreakpoint( DebuggerDriver* driver, cb::shared_ptr<DebuggerBreakpoint> bp )
      : DebuggerCmd( driver ),
        m_BP( bp ) {
      if( !bp ) {
        m_Cmd << _T( "bc *" );
      } else
      { m_Cmd << _T( "bc " ) << F( _T( "%d" ), ( int ) bp->index ); }
    }
    void ParseOutput( const wxString& output ) {
      if( !output.IsEmpty() ) {
        m_pDriver->Log( output );
      }
    }

    cb::shared_ptr<DebuggerBreakpoint> m_BP;
};


class CdbCmd_Watch : public DebuggerCmd {
    cb::shared_ptr<GDBWatch> m_watch;
  public:
    CdbCmd_Watch( DebuggerDriver* driver, cb::shared_ptr<GDBWatch> const &watch )
      : DebuggerCmd( driver ),
        m_watch( watch ) {
      wxString symbol;
      m_watch->GetSymbol( symbol );
      m_Cmd << _T( "?? " ) << symbol;
    }

    void ParseOutput( const wxString& output ) {
      if( !ParseCDBWatchValue( m_watch, output ) ) {
        wxString symbol;
        m_watch->GetSymbol( symbol );
        wxString const &msg = _T( "Parsing CDB output failed for '" ) + symbol + _T( "'!" );
        m_watch->SetValue( msg );
        Manager::Get()->GetLogManager()->LogError( msg );
      }
    }
};


class CdbCmd_TooltipEvaluation : public DebuggerCmd {
    wxTipWindow* m_pWin;
    wxRect m_WinRect;
    wxString m_What;
  public:

    CdbCmd_TooltipEvaluation( DebuggerDriver* driver, const wxString& what, const wxRect& tiprect )
      : DebuggerCmd( driver ),
        m_pWin( 0 ),
        m_WinRect( tiprect ),
        m_What( what ) {
      m_Cmd << _T( "?? " ) << what;
    }
    void ParseOutput( const wxString& output ) {
      wxString tip = m_What + _T( "=" ) + output;
      if( m_pWin ) {
        ( m_pWin )->Destroy();
      }
      m_pWin = new wxTipWindow( ( wxWindow* )Manager::Get()->GetAppWindow(), tip, 640, &m_pWin, &m_WinRect );
    }
};


class CdbCmd_Backtrace : public DebuggerCmd {
  public:

    CdbCmd_Backtrace( DebuggerDriver* driver, bool switchToFirst )
      : DebuggerCmd( driver ),
        m_SwitchToFirst( switchToFirst ) {
      m_Cmd << _T( "k n" );
    }
    void ParseOutput( const wxString& output ) {
      m_pDriver->GetStackFrames().clear();
      wxArrayString lines = GetArrayFromString( output, _T( '\n' ) );
      if( !lines.GetCount() || !lines[0].Contains( _T( "ChildEBP" ) ) ) {
        return;
      }
      bool firstValid = true;
      bool sourceValid = false;
      cbStackFrame frameToSwitch;
      for( unsigned int i = 1; i < lines.GetCount(); ++i ) {
        if( reBT1.Matches( lines[i] ) ) {
          cbStackFrame sf;
          sf.MakeValid( true );
          long int number;
          reBT1.GetMatch( lines[i], 1 ).ToLong( &number );
          sf.SetNumber( number );
          sf.SetAddress( cbDebuggerStringToAddress( reBT1.GetMatch( lines[i], 2 ) ) );
          sf.SetSymbol( reBT1.GetMatch( lines[i], 4 ) );
          if( reBT2.Matches( lines[i] ) ) {
            sf.SetFile( reBT2.GetMatch( lines[i], 1 ), reBT2.GetMatch( lines[i], 2 ) );
            if( firstValid )
            { sourceValid = true; }
          }
          m_pDriver->GetStackFrames().push_back( cb::shared_ptr<cbStackFrame>( new cbStackFrame( sf ) ) );
          if( m_SwitchToFirst && sf.IsValid() && firstValid ) {
            firstValid = false;
            frameToSwitch = sf;
          }
        }
      }
      Manager::Get()->GetDebuggerManager()->GetBacktraceDialog()->Reload();
      if( !firstValid && sourceValid ) {
        Cursor cursor;
        cursor.file = frameToSwitch.GetFilename();
        frameToSwitch.GetLine().ToLong( &cursor.line );
        cursor.address = frameToSwitch.GetAddressAsString();
        cursor.changed = true;
        m_pDriver->SetCursor( cursor );
        m_pDriver->NotifyCursorChanged();
      }
    }
  private:
    bool m_SwitchToFirst;
};

class CdbCmd_SwitchFrame : public DebuggerCmd {
  public:
    CdbCmd_SwitchFrame( DebuggerDriver *driver, int frameNumber ) :
      DebuggerCmd( driver ) {
      if( frameNumber < 0 ) {
        m_Cmd = _T( "k n 1" );
      } else
      { m_Cmd = F( _T( ".frame %d" ), frameNumber ); }
    }

    virtual void ParseOutput( const wxString& output ) {
      wxArrayString lines = GetArrayFromString( output, _T( '\n' ) );
      for( unsigned ii = 0; ii < lines.GetCount(); ++ii ) {
        if( lines[ii].Contains( _T( "ChildEBP" ) ) ) {
          continue;
        } else if( reSwitchFrame.Matches( lines[ii] ) ) {
          Cursor cursor;
          cursor.file = reSwitchFrame.GetMatch( lines[ii], 4 );
          wxString const &line_str = reSwitchFrame.GetMatch( lines[ii], 5 );
          if( !line_str.empty() ) {
            line_str.ToLong( &cursor.line );
          } else
          { cursor.line = -1; }
          cursor.address = reSwitchFrame.GetMatch( lines[ii], 1 );
          cursor.changed = true;
          m_pDriver->SetCursor( cursor );
          m_pDriver->NotifyCursorChanged();
          Manager::Get()->GetDebuggerManager()->GetBacktraceDialog()->Reload();
          break;
        } else
        { break; }
      }
    }
};


class CdbCmd_InfoRegisters : public DebuggerCmd {
  public:

    CdbCmd_InfoRegisters( DebuggerDriver* driver )
      : DebuggerCmd( driver ) {
      m_Cmd << _T( "r" );
    }
    void ParseOutput( const wxString& output ) {
      cbCPURegistersDlg *dialog = Manager::Get()->GetDebuggerManager()->GetCPURegistersDialog();
      wxString tmp = output;
      while( tmp.Replace( _T( "\n" ), _T( " " ) ) )
        ;
      wxArrayString lines = GetArrayFromString( tmp, _T( ' ' ) );
      for( unsigned int i = 0; i < lines.GetCount(); ++i ) {
        wxString reg = lines[i].BeforeFirst( _T( '=' ) );
        wxString addr = lines[i].AfterFirst( _T( '=' ) );
        if( !reg.IsEmpty() && !addr.IsEmpty() ) {
          dialog->SetRegisterValue( reg, addr, wxEmptyString );
        }
      }
    }
};


class CdbCmd_Disassembly : public DebuggerCmd {
  public:
    CdbCmd_Disassembly( DebuggerDriver* driver, const wxString& StopAddress )
      : DebuggerCmd( driver ) {
      m_Cmd << _T( "uf " ) << StopAddress;
    }
    void ParseOutput( const wxString& output ) {
      cbDisassemblyDlg *dialog = Manager::Get()->GetDebuggerManager()->GetDisassemblyDialog();
      wxArrayString lines = GetArrayFromString( output, _T( '\n' ) );
      for( unsigned int i = 0; i < lines.GetCount(); ++i ) {
        if( reDisassembly.Matches( lines[i] ) ) {
          uint64_t addr = cbDebuggerStringToAddress( reDisassembly.GetMatch( lines[i], 1 ) );
          dialog->AddAssemblerLine( addr, reDisassembly.GetMatch( lines[i], 2 ) );
        }
      }
    }
};


class CdbCmd_DisassemblyInit : public DebuggerCmd {
    static wxString LastAddr;
  public:
    CdbCmd_DisassemblyInit( DebuggerDriver* driver )
      : DebuggerCmd( driver ) {
      m_Cmd << _T( "k n 1; ln" );
    }
    void ParseOutput( const wxString& output ) {
      cbDisassemblyDlg *dialog = Manager::Get()->GetDebuggerManager()->GetDisassemblyDialog();
      long int offset = 0;
      wxArrayString lines = GetArrayFromString( output, _T( '\n' ) );
      for( unsigned int i = 0; i < lines.GetCount(); ++i ) {
        if( lines[i].Contains( _T( "ChildEBP" ) ) ) {
          if( reDisassemblyFile.Matches( lines[i + 1] ) ) {
            ++i;
            cbStackFrame sf;
            wxString addr = reDisassemblyFile.GetMatch( lines[i], 1 );
            sf.SetSymbol( reDisassemblyFile.GetMatch( lines[i], 2 ) );
            wxString offsetStr = sf.GetSymbol().AfterLast( _T( '+' ) );
            if( !offsetStr.IsEmpty() )
            { offsetStr.ToLong( &offset, 16 ); }
            if( addr != LastAddr ) {
              LastAddr = addr;
              sf.SetAddress( cbDebuggerStringToAddress( addr ) );
              sf.MakeValid( true );
              dialog->Clear( sf );
              m_pDriver->QueueCommand( new CdbCmd_Disassembly( m_pDriver, sf.GetSymbol() ) ); // chain call
            }
          }
        } else {
          m_pDriver->Log( _T( "Checking for current function start" ) );
          if( reDisassemblyFunc.Matches( lines[i] ) ) {
            uint64_t start = cbDebuggerStringToAddress( reDisassemblyFunc.GetMatch( lines[i], 1 ) );
            dialog->SetActiveAddress( start + offset );
          }
        }
      }
    }
};
wxString CdbCmd_DisassemblyInit::LastAddr;

#endif
